home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Games / TileSlide / Source / SoundGenerator.m < prev    next >
Text File  |  1993-07-10  |  12KB  |  360 lines

  1. /*
  2.  * SoundGenerator.m
  3.  * Author: Ali Ozer, with assistance from David Jaffe and Doug Keislar.
  4.  * Written for 1.0 5-May-89.
  5.  * Last updated for 1.0 on 15-Aug-89.
  6.  * Updated for 2.0 on 5-Sep-90; separated the panel into its own .nib file.
  7.  * Updated for 2.0 on 9-Oct-90 by David Jaffe and Ali Ozer: changed Music Kit 
  8.  *    code to be multi-threaded and timed, and made various other changes. 
  9.  *
  10.  * This class allows you to generate notes through the MusicKit; create an 
  11.  * instance of this class, enable it, then use playNote: to play notes.
  12.  * Currently the code in here isn't too general; you're stuck with no
  13.  * more than NUMNOTES notes simultaneously, for instance, and not all
  14.  * parameters can be changed at runtime.
  15.  * 
  16.  * SoundGenerator provides various IB-style target-action methods and
  17.  * outlets to allow hooking up a user interface.  If you do hook up input
  18.  * devices, pay attention to the various ranges of values allowed (see
  19.  * comments below). Also note that the modulator index and frequency
  20.  * multiplier input devices should be on a log scale; thus the values
  21.  * used are actually equal to 10.0^(input value). 
  22.  *
  23.  *  You may freely copy, distribute and reuse the code in this example.
  24.  *  NeXT disclaims any warranty of any kind, expressed or implied,
  25.  *  as to its fitness for any particular use.
  26.  */
  27.  
  28. // These values should be the same as those in the nib file
  29. #define DEFAULTATTACK 0.14  // In seconds; a good range is 0.005..1
  30. #define MINATTACK 0.005        // Never below this with the current setup
  31. #define DEFAULTDECAY 3    // In seconds; 0.01 to 10 or 20
  32. #define MINDECAY 0.01        // Some minimum value for decay...
  33. #define DEFAULTRATIO 1.0    // Should range from 0 on up 
  34. #define DEFAULTINDEX 0.1    // 0 to 20, perhaps
  35. #define DEFAULTAMP 0.2        // 0 to 1
  36. #define DEFAULTTRANSPOSE 0     // -12 to 12, perhaps
  37.  
  38. #import <math.h>
  39. #import <appkit/Application.h>
  40. #import <appkit/Button.h>
  41. #import <appkit/Panel.h>
  42.  
  43. @implementation SoundGenerator
  44.  
  45. // Call new or alloc/init to create the object. init does not grab the DSP;
  46. // it simply allocates and initializes the notes and various other
  47. // things. You need to send enable to claim the DSP.
  48.  
  49. - init
  50. {
  51.     int i;
  52.     double yArray[] = {0,1,0};
  53.     double xArray[] = {0,1,2};    
  54.  
  55.     [super init];
  56.  
  57.     // Create a very simple Envelope. The time and value scaling is arbitrary 
  58.     //               here because they'll be rescaled by the Note parameters.
  59.     //               The envelope has 1 attack segment.
  60.     ampEnvelope = [[Envelope allocFromZone:[self zone]] init];
  61.     [ampEnvelope setPointCount:3 
  62.          xArray:xArray    // Arrays are copied by the object
  63.           orSamplingPeriod:0.0
  64.          yArray:yArray
  65.          smoothingArray:NULL
  66.           orDefaultSmoothing:0.2];
  67.     [ampEnvelope setStickPoint:1];// Stickpoint is 1 (numbering from 0)
  68.  
  69.     transpose = DEFAULTTRANSPOSE;
  70.  
  71.     // Create the notes with some default values
  72.     for (i = 0; i < NUMNOTES; i++) {
  73.         notes[i] = [[Note allocFromZone:[self zone]] init];
  74.       [notes[i] setPar:MK_amp toDouble:DEFAULTAMP];      // amp scaling
  75.     [notes[i] setPar:MK_ampRel toDouble:DEFAULTDECAY]; // amp release time
  76.     [notes[i] setPar:MK_ampAtt toDouble:DEFAULTATTACK];// amp attack time
  77.     [notes[i] setDur:DEFAULTATTACK];                   // duration
  78.         // The duration is set to the same value as the attack time
  79.         //     so that the note proceeds immediately from the attack to the 
  80.         //     release.  Keep in mind that duration (dur) is defined as
  81.         //     the time from the start of the note to the release.
  82.     [notes[i] setNoteTag:MKNoteTag()];             // NUMNOTES "phrases"
  83.       [notes[i] setPar:MK_ampEnv toEnvelope:ampEnvelope];        
  84.       [notes[i] setPar:MK_m1Ratio toDouble:DEFAULTRATIO];
  85.       [notes[i] setPar:MK_m1Ind1 toDouble:DEFAULTINDEX];
  86.     }
  87.  
  88.     // Create a SynthInstrument to manage SynthPatches of class Fm1.
  89.     instrument = [[SynthInstrument allocFromZone:[self zone]] init];
  90.     [instrument setSynthPatchClass:[Fm1 class]];
  91.  
  92.     orchestra = [Orchestra new];
  93.     [orchestra setFastResponse:YES];    // Request minimum sound buffering 
  94.     [orchestra setTimed:YES];        // We'll queue events 100 ms early
  95.     MKSetDeltaT(.1);                    // See comment below
  96.  
  97.     /*
  98.     For fastest response, we could run the orchestra in "untimed
  99.     mode", which means that DSP commands are executed as soon as
  100.     they're received.  However, since envelopes (in the SynthPatch
  101.     Fm1) are fed one breakpoint at a time to the DSP by the host,
  102.     and since there is buffering in the sound driver, very small
  103.     attacks can get lost.  If you want very short attacks, there
  104.     are two ways to go:
  105.     
  106.     1) Set the orchestra to timed mode and include a "delta time" of .1 by 
  107.        calling MKSetDeltaT(.1), which gives the DSP a 100 ms cushion.
  108.     
  109.     2) Change the envelope to begin at 1 instead of 0 when the attack is 
  110.        small
  111.     
  112.     We use solution 1 above.
  113.     */
  114.  
  115.     // Now set some aspects of the performance:
  116.     // Don't quit when the Conductor's messages-to-send queue is empty.
  117.     [Conductor setFinishWhenEmpty:NO];
  118.  
  119.     // Run performance in separate high-priority thread for independence 
  120.     // between animation and music
  121.     [Conductor useSeparateThread:YES];  
  122.     [Conductor setThreadPriority:1.0];
  123.  
  124.     return self;
  125. }    
  126.  
  127. // The enable method tries to claim the DSP; returns YES if 
  128. // successful. Call disable to let go of the DSP so that other apps can use 
  129. // it.
  130.     
  131. -(BOOL) enable
  132. {
  133.     if ([Conductor inPerformance]) return YES;     // Already enabled
  134.  
  135.     if (![orchestra open]) {
  136.     return NO;    // To indicate that we could not grab the DSP
  137.     }
  138.  
  139.     currentNote = 0;     
  140.  
  141.     // Set the maximum number of notes to play at any one time. This actually
  142.     //    allocates and loads the patches on the DSP. Hence, it must be
  143.     //    done after the Orchestra is open.
  144.     if ([instrument setSynthPatchCount:NUMNOTES] != NUMNOTES) {
  145.     [orchestra close];
  146.     return NO;
  147.     }
  148.  
  149.     // Start the DSP running and start the performance in a separate thread
  150.     [orchestra run];                
  151.     [Conductor startPerformance]; 
  152.  
  153.     // Get settings from the sound parameters panel, if initialized.
  154.     if (attackInput) {  /* Panel initialized? */
  155.        [self changeAttack:attackInput];
  156.        [self changeDecay:decayInput];
  157.        [self changeRatio:ratioInput];
  158.        [self changeIndex:indexInput];
  159.        [self changeTranspose:transposeInput];
  160.     }
  161.     return YES;
  162. }
  163.  
  164. - disable
  165. {
  166.     if (![Conductor inPerformance]) return self;
  167.  
  168.     [Conductor lockPerformance];    // Get Music Kit thread lock
  169.     [Conductor finishPerformance];  // Finishes performance
  170.     [orchestra abort];              // Closes Orchestra without waiting
  171.     [Conductor unlockPerformance];  // Gives up Music Kit thread lock
  172.   
  173.     return self;
  174. }
  175.  
  176. // playNoteAtFreq: will play a note at the specified frequency using the
  177. // parameters set with the various change... methods below.
  178.  
  179. - playNoteAtFreq:(double)freq
  180. {
  181.     if (![Conductor inPerformance]) return self;
  182.     
  183.     // Update Conductor's notion of the current time; lock performance thread
  184.     [Conductor lockPerformance];
  185.  
  186.     // Use the supplied frequency 
  187.     [notes[currentNote] setPar:MK_freq toDouble:freq*pow((double)2,(double)(transpose/12.0))]; 
  188.  
  189.     // Send the note off
  190.     [[instrument noteReceiver] receiveNote:notes[currentNote]]; 
  191.  
  192.     // Send any buffered DSP commands and unlock performance thread
  193.     [Conductor unlockPerformance];
  194.  
  195.     currentNote = (currentNote + 1) % NUMNOTES;
  196.  
  197.     return self;
  198. }
  199.  
  200. // The free method gets rid of the object for good.
  201.  
  202. - free
  203. {
  204.     [self disable];
  205.     [ampEnvelope free];
  206.     {int i; for (i = 0; i < NUMNOTES; i++) [notes[i] free];}
  207.     return [super free];
  208. }
  209.  
  210. // Invoke shutUp to stop all the notes from generating sound.
  211.  
  212. - shutUp
  213. {
  214.     int i;
  215.  
  216.     if (![Conductor inPerformance]) return self;
  217.  
  218.     [Conductor lockPerformance];     // Get lock 
  219.     for (i = 0; i < NUMNOTES; i++) { 
  220.     [notes[i] setPar:MK_amp toDouble:0.0];           // zero amplitude
  221.         [[instrument noteReceiver] receiveNote:notes[i]];// send notes
  222.     [notes[i] setPar:MK_amp toDouble:DEFAULTAMP];    // restore amplitude
  223.     }
  224.     [Conductor unlockPerformance];   // Give lock
  225.  
  226.     return self;
  227. }
  228.  
  229. // changeDecayParameter: and changeAttackParameter: allow you to set
  230. // the decay & attack times (in seconds).
  231.  
  232. - changeDecayParameter:(double)newDecay
  233. {
  234.     newDecay = MAX(newDecay,MINDECAY);    // Clip to reasonable value
  235.     [self changeParameter:MK_ampRel toDouble:newDecay];
  236.     return self;
  237. }
  238.  
  239. - changeAttackParameter:(double)newAttack
  240. {
  241.     int noteCnt;
  242.     newAttack = MAX(newAttack,MINATTACK);  // Clip to reasonable value
  243.     [self changeParameter:MK_ampAtt toDouble:newAttack];
  244.     for (noteCnt = 0; noteCnt < NUMNOTES; noteCnt++) {
  245.     [notes[noteCnt] setDur:newAttack];
  246.     }
  247.     return self;
  248. }
  249.  
  250. // The following method initializes an already allocated amplitude envelope
  251. // with values. The envelope has a sharp attack and a decay determined by
  252. // the decayTime parameter.
  253.  
  254.  
  255. // The following method allows you to change a certain parameter for all
  256. // the notes. Only "toDouble:" version is provided. Note that the changes
  257. // do not take effect until the Notes are sent to the instrument. 
  258.  
  259. - changeParameter:(int)param toDouble:(double)value
  260. {
  261.     int i;
  262.     [Conductor lockPerformance];
  263.     for (i = 0; i < NUMNOTES; i++) {
  264.     [notes[i] setPar:param toDouble:value];
  265.     }
  266.     [Conductor unlockPerformance];
  267.     return self;
  268. }
  269.  
  270. // Function to round floating point numbers to certain number of
  271. // decimal digits. decDigits should be 0..4.
  272.  
  273. static double RoundTo (double num, int decDigits)
  274. {
  275.     static double pow10[] = {1, 10., 100, 1000., 10000.};
  276.     return (rint(num * pow10[decDigits])) / pow10[decDigits];
  277. }
  278.  
  279. // The following three methods allow IB-style target/action input devices
  280. // (like sliders) to set the values of the three-settable parameters of the 
  281. // sound generator object, m1 ratio, m1 index, and decay time.
  282. // After the values are set, they are displayed on the (optional) outputs.
  283.  
  284. - changeRatio:sender
  285. {
  286.     double newValue = RoundTo([sender doubleValue], 1);
  287.     [self changeParameter:MK_m1Ratio toDouble:newValue];
  288.     [ratioOutput setDoubleValue:newValue];
  289.     return self;
  290. }
  291.  
  292. - changeIndex:sender
  293. {
  294.     double newValue = RoundTo(pow(10.0, [sender doubleValue]) - 1.0, 1);
  295.     [self changeParameter:MK_m1Ind1 toDouble:newValue];
  296.     [indexOutput setDoubleValue:newValue];
  297.     return self;
  298. }
  299.  
  300. - changeDecay:sender
  301. {
  302.     double newValue = RoundTo([sender doubleValue], 1);
  303.     [self changeDecayParameter:newValue];
  304.     [decayOutput setDoubleValue:newValue];
  305.     return self;
  306. }
  307.  
  308. - changeAttack:sender
  309. {
  310.     double newValue = RoundTo([sender doubleValue], 2);
  311.     [self changeAttackParameter:newValue];
  312.     [attackOutput setDoubleValue:newValue];
  313.     return self;
  314. }
  315.  
  316. - changeTranspose:sender
  317. {
  318.     transpose = [sender intValue];
  319.     [transposeOutput setIntValue:transpose];
  320.     return self;
  321. }
  322.  
  323. - changeState:sender
  324. {
  325.     if ([sender state]) {
  326.     if (![self enable]) {
  327.         NXRunAlertPanel (NULL, "Can't open the DSP.", "OK", NULL, NULL);
  328.         [sender setState:NO];
  329.     }
  330.     } else {
  331.     [self disable];
  332.     }
  333.     return self;
  334. }
  335.  
  336. - play:sender
  337. {
  338.     [self playNoteAtFreq:[sender doubleValue]];
  339.     return self;
  340. }
  341.  
  342. // Method to load .nib files for the sound settings panel.
  343.  
  344. - showSoundSettings:sender
  345. {
  346.     if (!soundSettingsPanel) {
  347.     [NXApp loadNibSection:"SoundSettings.nib"
  348.             owner:self
  349.             withNames:NO
  350.             fromZone:[self zone]];
  351.     }
  352.     [soundSettingsPanel makeKeyAndOrderFront:sender];
  353.     return self;
  354. }
  355.  
  356.  
  357. @end
  358.    
  359.  
  360.